package ch.exm.storm.store;

import static ch.exm.storm.introspection.IntrospectionUtil.getAllAncestors;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import ch.exm.storm.loader.ObjectLoadListener;
import ch.exm.storm.loader.ObjectLoader;
import ch.exm.storm.loader.hibernate.HibernateLoader;
import ch.exm.storm.log.Logger;

public class Cache implements ObjectStore, ObjectLoadListener {
	
	private ObjectLoader loader;
	
	public Cache() {
		loader = new HibernateLoader();
		init();
	}
	
	private void init() {
		loader.loadAll(this);
	}

	// ////////////////////////////////////////////////////////////////////////
	// /// ObjectStore
	// ////////////////////////////////////////////////////////////////////////
	
	private Map<Class<?>, Map<Object, Object>> cache = new HashMap<Class<?>, Map<Object, Object>>(); 
	
	public <T> Collection<T> getAll(Class<T> clazz) {
		Collection<T> result = new ArrayList<T>();
		Set<Class<?>> mappedClasses = mappedClassesMap.get(clazz);
		if (mappedClasses == null) return result;
		for (Class<?> mappedClass : mappedClasses) {
			Map<Object, Object> classCache = cache.get(mappedClass);
			if (classCache != null) result.addAll((Collection<T>)classCache.values());
		}
		return result;
	}

	public <T> T getById(Class<T> clazz, Object id) {
		Set<Class<?>> mappedClasses = mappedClassesMap.get(clazz);
		if (mappedClasses == null) return null;
		for (Class<?> mappedClass : mappedClasses) {
			Map<Object, Object> classCache = cache.get(mappedClass);
			if (classCache != null) {
				Object result = classCache.get(id);
				if (result != null) return (T)result;
			}
		}
		return null;
	}

	// ////////////////////////////////////////////////////////////////////////
	// /// ObjectLoadListener
	// ////////////////////////////////////////////////////////////////////////
	
	public Object onObjectLoaded(Class<?> objectClass, Object id, Object object) {
		if (objectClass == null) objectClass = object.getClass();
		String loadedObjectAsString = "Object of class " + objectClass.getName() + " with id " + id;
		Logger.debug(this, "Loaded " + loadedObjectAsString);
		Map<Object, Object> classCache = cache.get(objectClass);
		if (classCache == null) {
			indexMappedClass(objectClass);
			classCache = new HashMap<Object, Object>();
			cache.put(objectClass, classCache);
		}
		Object objectInCache = classCache.get(id);
		if (objectInCache == null) {
			classCache.put(id, object);
			Logger.debug(this, loadedObjectAsString + " added to cache");
		} else {
			Logger.debug(this, loadedObjectAsString + " already present in cache");
		}
		return objectInCache;
	}

	// ////////////////////////////////////////////////////////////////////////
	// /// MappedClassesMap
	// ////////////////////////////////////////////////////////////////////////
	
	private Map<Class<?>, Set<Class<?>>> mappedClassesMap = new HashMap<Class<?>, Set<Class<?>>>();
	
	private void indexMappedClass(Class<?> mappedClass) {
		for (Class<?> ancestor : getAllAncestors(mappedClass)) {
			Set<Class<?>> mappedAncestors = mappedClassesMap.get(ancestor);
			if (mappedAncestors == null) {
				mappedAncestors = new HashSet<Class<?>>();
				mappedClassesMap.put(ancestor, mappedAncestors);
			}
			mappedAncestors.add(mappedClass);
		}
	}
}
